#include "Cliente.h"

Cliente::Cliente() {
	connected = false;
}

Cliente::Cliente(Socket* un_socket, std::string& storage_path,
		std::string& index_path, std::string& login_path,
		int polling_interval) {
	sock = un_socket;
	this->storage_path = storage_path;
	this->login_path = login_path;
	m = new Mutex();
	this->index_path = index_path;
	poller = new ClientPolling(sock, m, polling_interval, index_path,
			storage_path, this->user, this->pass);
	connected = false;

}

void Cliente::initialize(Socket* un_socket, std::string& storage_path,
		std::string& index_path, std::string& login_path, int polling_interval,
		std::string user, std::string pass) {
	sock = un_socket;
	this->storage_path = storage_path;
	this->login_path = login_path;
	m = new Mutex();
	this->index_path = index_path;
	poller = new ClientPolling(sock, m, polling_interval, index_path,
			storage_path, this->user, this->pass);
	connected = true;
}

void Cliente::setSocket(Socket* un_socket) {
	sock = un_socket;
}

std::string Cliente::getUser() {
	return user;
}

std::string Cliente::getPass() {
	return pass;
}
void Cliente::process_msg(Message* msg, int id) {
	switch (id) {

	case 4: {
		category.notice("No files to request");
		delete msg;

		break;
	}
	case 5: {
		category.notice("List received by server");
		FilesList *fl = &dynamic_cast<FilesList&>(*msg);
		this->recv_list_files(fl);
		delete fl;

		break;
	}
	case 7: {
		FileNotFound *fnf = &dynamic_cast<FileNotFound&>(*msg);
		category.notice("File " + fnf->getNameFile() + " not found");
		std::string name = fnf->getNameFile();
		this->delete_in_wait(name); // Como no existe mas, lo borra
		delete fnf;

		break;
	}
	case 9: {
		PutFile *pf = &dynamic_cast<PutFile&>(*msg);
		category.notice("Received file " + pf->getFileName());
		//update map y bajada de archivo
		this->update_state(pf);
		delete pf;

		break;
	}
	case 10: {

		PutOk *pok = &dynamic_cast<PutOk&>(*msg);
		std::string name_file = pok->getFileName();
		category.notice("Sent " + name_file);
		delete pok;

		this->delete_in_tosend(name_file); // Marca como que el cliente lo recibio

		break;
	}
	case 11: {
		PutError *perr = &dynamic_cast<PutError&>(*msg);
		category.error(
				"Put Error. Could not send the file: " + perr->getFileName());
		delete msg;
		break;
	}
	case 12: {
		Notify *ny = &dynamic_cast<Notify&>(*msg);
		char type = ny->getType();
		std::string file_name = ny->getFileName();
		if (type == 'A' || type == 'M') {
			category.notice("NOTIFY received. Add file: " + ny->getFileName());
			this->get_file(file_name); // Pide archivo
		} else {
			category.notice(
					"NOTIFY received. Delete file: " + ny->getFileName());
			this->delete_file(file_name); // Borra el archivo
		}
		delete ny;
		break;
	}
	case 16: {

		DeleteOk *dok = &dynamic_cast<DeleteOk&>(*msg);
		category.notice("Delete OK the file " + dok->getFileName());
		delete msg;
		break;
	}

	case 17: {
		DeleteError *derr = &dynamic_cast<DeleteError&>(*msg);
		category.error(
				"Delete Error the file " + derr->getFileName()
						+ ". Perhaps had already been removed");
		delete msg;
		break;
	}
	}
}

void Cliente::process(Message *msg, int id) {
	to_process tp;
	tp.msg_ = msg;
	tp.id_ = id;
}

void Cliente::setUserPass(std::string& user, std::string& pass) {
	this->user = user;
	this->pass = pass;
}

void Cliente::listen_server() {
	int bytes;
	std::string total_received;

	while (r) {
		try {
			total_received = sock->RecvNextMsg();
			std::string signature = total_received.substr(5,
					total_received.size() - 5);
			if (is_authentic(signature, user, pass)) {
				Decoder d;
				std::string message = total_received.substr(0,
						total_received.size() - BYTESMD5);
				Message *msg = d.parser(message);
				this->process_msg(msg, d.get_id_msg());
			}
		} catch (CommunicationException& e) {
			category.error("Se interrumpio la comunicacion con el Servidor");
			throw CommunicationException();

			break;
		}
	}
}

void Cliente::process_logging(int id, bool* logging_error,
		bool *not_max_tries) {
	switch (id) {
	case 1:
		*logging_error = false;
		break;
	case 2:
		category.error("Login Error");
		break;
	case 14:
		*not_max_tries = false;
		break;
	}
}

void Cliente::update_state(PutFile *pf) {
	std::string name = pf->getFileName();
	std::string file = pf->getFile();
	std::string hash = pf->getHash();
	std::string path_name = this->storage_path + "/" + name;
	m->lock();

	//Se bloquea para leer el archivo y calcular su hash, ademas de agregar al indice y grabar en disco
	indice.LoadFromDisk();
	indice.AddKey(&name, &hash);
	indice.SaveToDisk();
	HexEncode he(path_name.c_str());
	he.Desencode(&file);

	m->unlock(); //desbloqueo del mutex
	this->delete_in_wait(name); // Borra de el vector de espera
}

void Cliente::register_to_server(std::string& user, std::string& pass) {
	Register *r = new Register(&user, &pass);
	sock->Send(r);
	delete r;

}

void Cliente::get_list_files() {
	GetFilesList *gfl = new GetFilesList(this->user, this->pass);
	sock->Send(gfl); // TODO(TRY CATCH MAS AFUERA) veremos que hacemos con esto
	delete gfl;
}

void Cliente::recv_list_files(FilesList* list) {
	std::map < std::string, std::string > map = list->getMap();
	std::vector < std::string > to_get = indice.Differences(map);

	// Pidiendo archivos 'novedades'
	for (int i = 0; i < to_get.size(); i++) {
		category.notice("Request file " + to_get[i]);
		this->get_file(to_get[i]);
	}

}

void Cliente::get_file(std::string& name_file) {
	GetFile *gf = new GetFile(&name_file, this->user, this->pass);
	sock->Send(gf);
	inWait.push_back(name_file); // Lo pide y lo pone en espera
	delete gf;
}

void Cliente::put_file(std::string& name_file) {
	std::string path_name = storage_path + name_file;
	m->lock();
	//Se bloquea para levantar el archivo y calcular el hash
	std::string binary_file = HexEncode::FileToString(path_name.c_str());
	HexEncode he(path_name.c_str());
	std::string hexa = he.Encode(); // pasa a hexa el archivo
	std::string hash = md5(binary_file); //calcula hash

	m->unlock(); // desloqueo mutex

	PutFile *pf = new PutFile(&name_file, &hash, &hexa, this->user, this->pass);
	sock->Send(pf);
	delete pf;

	to_send.AddKey(&name_file, &hash); // Marco en archivos para enviar
}

void Cliente::delete_in_wait(std::string& name_file) {
	std::vector<std::string>::iterator i = inWait.begin();
	for (std::vector<std::string>::iterator endi = inWait.end(); i != endi;
			i++) {
		if (*i == name_file) {
			inWait.erase(i);
			break;
		}
	}
}

void Cliente::delete_in_tosend(std::string& name_file) {
	to_send.DeleteKey(&name_file);
}

void Cliente::delete_file(std::string& name_file) {
	std::string path_name = this->storage_path + "/" + name_file;
	const char* file_path_name = path_name.c_str();

	m->lock();
	//Bloqueo para actualizar indice

	remove(file_path_name);
	indice.LoadFromDisk();
	indice.DeleteKey(&name_file);
	indice.SaveToDisk();

	m->unlock();
}
bool Cliente::is_connected() {
	return connected;
}
void Cliente::run() {
	this->get_list_files();
	m->lock();
	indice.setIndexPath(&this->index_path);
	indice.LoadFromDisk(); // carga los archivos con sus hashes
	m->unlock();
	try {
		poller->start(); //Lanzo el poller
		this->listen_server();
	} catch (CommunicationException& e) {
		category.notice("End sincronization. Closing connection");
		poller->stop();
		poller->join();
		sock->Shutdown(2);
		connected = false;
	}
}

Cliente::~Cliente() {
	delete m;
	delete poller;
}

